Auto-resume agent sessions on app restore#3025
Auto-resume agent sessions on app restore#3025yourconscience wants to merge 3 commits intomanaflow-ai:mainfrom
Conversation
|
@yourconscience is attempting to deploy a commit to the Manaflow Team on Vercel. A member of the Team first needs to authorize it. |
📝 WalkthroughWalkthroughThis change implements agent resume command tracking and restoration across the session management system. It adds a new API to fetch recent session entries, extends session persistence with resume command storage, integrates resolution calls in terminal control, and implements comprehensive caching and restoration logic in Workspace. Changes
Sequence DiagramsequenceDiagram
participant TC as TerminalController
participant WS as Workspace
participant SIS as SessionIndexStore
participant SP as SessionPersistence
rect rgba(100, 150, 200, 0.5)
Note over TC,SP: Live Session - Resolve & Cache Resume Command
TC->>WS: resolveAndCacheResumeCommand(agentKey, pid)
WS->>WS: ttyForPID(pid) via sysctl
WS->>SIS: latestEntries(agent, cwd, limit)
SIS-->>WS: [SessionEntry with resumeCommand]
WS->>WS: Cache resumeCommand in cachedAgentResumeCommands[panelUUID]
end
rect rgba(150, 100, 200, 0.5)
Note over TC,SP: Session Restoration - Replay Resume Commands
TC->>WS: Session restore begins
WS->>SP: Snapshot includes agentResumeCommand
SP-->>WS: SessionTerminalPanelSnapshot
WS->>WS: Compute initialInput from snapshot.agentResumeCommand
WS->>TC: newTerminalSurface with initialInput
TC->>TC: Terminal created with resumed command
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
Sources/SessionIndexStore.swift (1)
525-528: Minor: unusedErrorBagon the Claude branch.
loadClaudeEntriesdoes not accept anerrorBag, so errors on the Claude path are silently swallowed here (consistent withscanAll, but worth a note). If future work adds error reporting to the Claude loader, remember to threadbagthrough; otherwise thelet bag = ErrorBag()allocation is unused for.claude. Not blocking.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/SessionIndexStore.swift` around lines 525 - 528, The code creates an unused ErrorBag instance (ErrorBag) before switching on agent and immediately returns from the .claude branch by calling loadClaudeEntries(needle:cwdFilter:offset:limit:), so the ErrorBag allocation is redundant and any errors on the Claude path aren’t captured; either remove the unused let bag = ErrorBag() or, if you intend to support error reporting for Claude in future, modify loadClaudeEntries to accept an ErrorBag parameter and thread bag through (also mirror how scanAll uses ErrorBag) so errors are reported rather than silently dropped.Sources/ContentView.swift (1)
2841-2850: Consider falling back to any available panel whenfocusedPanelIdis nil.When no panel is focused (e.g., the user opened the Files panel before interacting with any terminal/editor), the markdown route is skipped entirely and the file opens in an external editor, silently ignoring the
openMarkdownInCmuxViewerpreference. A similar pattern exists elsewhere inWorkspace(e.g.,openEditor(filePath:)falls back tobonsplitController.allPaneIds.firstwhenfocusedPaneIdis nil) to avoid dropping requests.If
Workspaceexposes an analogous first-pane/first-panel fallback, preferring it over the unconditional external-editor path would better honor the user's setting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/ContentView.swift` around lines 2841 - 2850, The current onOpenMarkdown closure returns early to external editor when workspace.focusedPanelId is nil; change it to prefer any available panel by obtaining a fallback panel id (e.g., let panelId = workspace.focusedPanelId ?? workspace.bonsplitController.allPaneIds.first or a Workspace-provided firstPanelId) and pass that to workspace.openOrFocusMarkdownSplit(from: panelId, filePath: path); only call PreferredEditorSettings.open(URL(fileURLWithPath: path)) if openOrFocusMarkdownSplit returns nil. Update the code paths referencing tabManager.selectedWorkspace, workspace.focusedPanelId, workspace.openOrFocusMarkdownSplit(...), and PreferredEditorSettings.open(...) accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@Sources/SessionIndexStore.swift`:
- Around line 516-534: Add a new SessionEntry instance method
resumeCommandWithCwd() that returns the guarded resume command by shell-quoting
the SessionEntry.cwd and returning "cd <quoted cwd> && <resumeCommand>" (or just
the resumeCommand if cwd is empty); then update all resume-command call sites to
use it instead of the bare entry.resumeCommand—specifically change
resolveAndCacheResumeCommand(...) (where the value is cached for initialInput),
ContentView.resumeSession, the Workspace drag-drop terminal restore, and
SessionIndexView clipboard copy to call entry.resumeCommandWithCwd(); ensure the
shell-quoting used is the same utility/convention used elsewhere in the codebase
to prevent rc-file bypass.
In `@Sources/SessionPersistence.swift`:
- Around line 227-230: Add an isRemoteBacked Bool to
SessionTerminalPanelSnapshot and use it to prevent applying agentResumeCommand
to remote-backed panels: when restoring in the Workspace restore path (the code
that reads SessionTerminalPanelSnapshot and constructs resumeInput and calls
newTerminalSurface(... initialInput: ...)), only set resumeInput and pass
agentResumeCommand as initialInput if snapshot.isRemoteBacked is false (mirror
the existing gating used for initialInput and panelRestoreCommands). Ensure the
new isRemoteBacked field is populated in snapshots and used in the conditional
around the resumeInput assignment and the initialInput argument to
newTerminalSurface so remote-backed panels never receive detected or restore
commands.
In `@Sources/Workspace.swift`:
- Around line 472-475: The SessionTerminalPanelSnapshot is currently given
agentResumeCommand(forPanelId:) unconditionally, which allows persisted
initialInput to be replayed into remote-backed terminals; update the snapshot
construction (where SessionTerminalPanelSnapshot is created) to check the
panel's per-panel remote-backed flag (the same flag used by
createPanel(from:inPane:)) and only pass agentResumeCommand (initialInput) when
the panel is local (not remote-backed); apply the same guard to the other
occurrence around lines 707-712 so remote-backed panels never receive
initialInput from agentResumeCommand(forPanelId:).
- Around line 599-602: The code caches entry.resumeCommand directly, but you
must cache the cwd-guarded helper version so restored terminals start in the
intended directory; replace storing entry.resumeCommand into
cachedAgentResumeCommands[panelId] with the SessionEntry-provided cwd-guarded
resume command (use the helper on the SessionEntry instance, passing panelCwd so
it emits something like "cd <shell-quoted cwd> && <resumeCommand>"), i.e. call
the SessionEntry helper that builds the cwd-guarded resume command and store
that result instead of entry.resumeCommand.
- Around line 550-556: The stored property cachedAgentResumeCommands is declared
inside an extension and must be moved into the Workspace class body near
agentPIDs and restoredTerminalScrollbackByPanelId; remove the stored var from
the extension and keep only helper methods there. When populating the cache
(where entry.resumeCommand is used), store a cwd-guarded resume command (e.g.,
combine/validate the resume command with the panel's cwd or wrap with a guard
that checks cwd on replay) instead of the raw entry.resumeCommand. When
replaying cached commands (the code around SessionTerminalPanelSnapshot
handling), only replay for local terminals by gating on the per-panel
isRemoteBacked flag (skip replay if isRemoteBacked is true). Update references
to cachedAgentResumeCommands, the population site, and the replay site
accordingly so the extension methods access the moved property on Workspace.
---
Nitpick comments:
In `@Sources/ContentView.swift`:
- Around line 2841-2850: The current onOpenMarkdown closure returns early to
external editor when workspace.focusedPanelId is nil; change it to prefer any
available panel by obtaining a fallback panel id (e.g., let panelId =
workspace.focusedPanelId ?? workspace.bonsplitController.allPaneIds.first or a
Workspace-provided firstPanelId) and pass that to
workspace.openOrFocusMarkdownSplit(from: panelId, filePath: path); only call
PreferredEditorSettings.open(URL(fileURLWithPath: path)) if
openOrFocusMarkdownSplit returns nil. Update the code paths referencing
tabManager.selectedWorkspace, workspace.focusedPanelId,
workspace.openOrFocusMarkdownSplit(...), and PreferredEditorSettings.open(...)
accordingly.
In `@Sources/SessionIndexStore.swift`:
- Around line 525-528: The code creates an unused ErrorBag instance (ErrorBag)
before switching on agent and immediately returns from the .claude branch by
calling loadClaudeEntries(needle:cwdFilter:offset:limit:), so the ErrorBag
allocation is redundant and any errors on the Claude path aren’t captured;
either remove the unused let bag = ErrorBag() or, if you intend to support error
reporting for Claude in future, modify loadClaudeEntries to accept an ErrorBag
parameter and thread bag through (also mirror how scanAll uses ErrorBag) so
errors are reported rather than silently dropped.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2e2cceae-1dea-47f8-8c23-35835ce0854f
📒 Files selected for processing (7)
Sources/ContentView.swiftSources/FileExplorerView.swiftSources/RightSidebarPanelView.swiftSources/SessionIndexStore.swiftSources/SessionPersistence.swiftSources/TerminalController.swiftSources/Workspace.swift
Greptile SummaryThis PR adds two independent features: auto-resuming Claude Code / Codex / OpenCode sessions on app restore by persisting a synthesized resume command in the session snapshot, and routing the Files-panel "Open in Default Editor" action through the existing
Confidence Score: 4/5Safe to merge after fixing the stale-resume-command bug; the Files-panel routing change is clean. One concrete P1 defect: Sources/Workspace.swift — Important Files Changed
Sequence DiagramsequenceDiagram
participant Shell as Shell / Agent
participant TC as TerminalController
participant WS as Workspace
participant SIS as SessionIndexStore
participant Snap as SessionPersistence
Shell->>TC: report_tty ttys001
TC->>WS: surfaceTTYNames[panelId] = ttys001
Shell->>TC: set_agent_pid claude_code 12345
TC->>WS: resolveAndCacheResumeCommand(claude_code, 12345)
WS->>WS: ttyForPID(12345) returns ttys001
WS->>WS: match panelId via surfaceTTYNames
WS->>SIS: latestEntries(agent:.claude, cwd:/proj)
SIS-->>WS: SessionEntry with resumeCommand
WS->>WS: cachedAgentResumeCommands[panelId] = cmd
Note over TC,WS: Agent session finishes
Shell->>TC: clear_agent_pid claude_code
TC->>WS: agentPIDs.removeValue(claude_code)
Note over WS: cachedAgentResumeCommands NOT cleared
WS->>Snap: snapshotPanel includes agentResumeCommand
Note over Snap: App quits and relaunches
Snap->>WS: restorePanel with agentResumeCommand
WS->>Shell: initialInput = resume cmd + newline, spurious resume
|
There was a problem hiding this comment.
4 issues found across 7 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/SessionIndexStore.swift">
<violation number="1" location="Sources/SessionIndexStore.swift:532">
P2: `latestEntries` performs synchronous OpenCode DB/file I/O in an async method, so it can block the caller executor (including main actor).</violation>
</file>
<file name="Sources/Workspace.swift">
<violation number="1" location="Sources/Workspace.swift:556">
P2: New per-panel resume-command cache is not pruned with other panel metadata, allowing stale UUID entries to accumulate during normal panel churn.</violation>
<violation number="2" location="Sources/Workspace.swift:595">
P2: CWD-based resume command lookup runs in remote workspaces, which can map local indexed sessions into remote terminals.</violation>
<violation number="3" location="Sources/Workspace.swift:707">
P1: Agent resume command is replayed unconditionally — remote-backed panels will receive a local `claude --resume …` (or similar) as `initialInput`, injecting it into the remote bootstrap shell. Gate `resumeInput` on the panel not being remote-backed, consistent with other restore paths that skip `initialInput` for remote panels.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
There was a problem hiding this comment.
♻️ Duplicate comments (2)
Sources/Workspace.swift (2)
582-599:⚠️ Potential issue | 🟠 MajorGate by the matched panel and cache the cwd-guarded command.
guard !isRemoteWorkspaceskips local terminals that happen to live in a remote workspace; the remote check should be per matched panel. Also, this still stores rawentry.resumeCommand, so restored fresh shells can resume from the wrong cwd.🔧 Proposed fix
func resolveAndCacheResumeCommand(agentKey: String, pid: pid_t) { - guard !isRemoteWorkspace else { return } guard let agent = Self.sessionAgentForKey(agentKey) else { return } guard pid > 0 else { return } // Match PID to panel via TTY guard let pidTTY = Self.ttyForPID(pid) else { return } let matchedPanelId: UUID? = surfaceTTYNames.first(where: { $0.value == pidTTY })?.key guard let panelId = matchedPanelId else { return } + guard !remoteDetectedSurfaceIds.contains(panelId), + !isRemoteTerminalSurface(panelId) else { + cachedAgentResumeCommands.removeValue(forKey: panelId) + return + } let panelCwd = panelDirectories[panelId] ?? currentDirectory guard !panelCwd.isEmpty else { return } Task { let entries = await SessionIndexStore.latestEntries(agent: agent, cwd: panelCwd, limit: 1) guard let entry = entries.first else { return } + let resumeCommand = entry.resumeCommandWithCwd await MainActor.run { - self.cachedAgentResumeCommands[panelId] = entry.resumeCommand + guard self.panels[panelId] != nil, + !(self.remoteDetectedSurfaceIds.contains(panelId) || self.isRemoteTerminalSurface(panelId)) else { + return + } + self.cachedAgentResumeCommands[panelId] = resumeCommand } } }Based on learnings: “always build resume commands using the cwd guard helper exposed by SessionEntry …
cd <shell-quoted cwd> && <resumeCommand>” and “remote-backed terminals … omit restore/detected commands.”🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/Workspace.swift` around lines 582 - 599, The function resolveAndCacheResumeCommand currently gates using the global isRemoteWorkspace and stores raw entry.resumeCommand; change it to determine remote status per matched panel (use matchedPanelId and panelDirectories) and skip caching if that panel is remote-backed, then build and cache a guarded resume command using the SessionEntry helper (i.e., prepend a cwd-guarding cd with proper shell-quoting of panelCwd so the stored command is "cd <shell-quoted cwd> && <entry.resumeCommand>") instead of storing raw resumeCommand; use Self.sessionAgentForKey, Self.ttyForPID, surfaceTTYNames, SessionIndexStore.latestEntries, SessionEntry.resumeCommand and cachedAgentResumeCommands to locate and update the code paths.
703-708:⚠️ Potential issue | 🟠 MajorKeep the restore-time remote gate on
initialInput.This replays
agentResumeCommanddirectly from the snapshot. Please also gate replay with the per-panel remote-backed snapshot flag, so an older or malformed snapshot cannot inject a local resume command into a remote startup shell.🛡️ Proposed fix
- let resumeInput = snapshot.terminal?.agentResumeCommand.map { $0 + "\n" } + let panelWasRemoteBacked = snapshot.terminal?.isRemoteBacked == true + let resumeInput = panelWasRemoteBacked + ? nil + : snapshot.terminal?.agentResumeCommand.map { $0 + "\n" } guard let terminalPanel = newTerminalSurface( inPane: paneId, focus: false, workingDirectory: workingDirectory, initialInput: resumeInput,Based on learnings: “createPanel(from:inPane:) uses this per-panel flag … pass initialInput only for local panels.”
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/Workspace.swift` around lines 703 - 708, The code currently replays snapshot.terminal?.agentResumeCommand into newTerminalSurface via initialInput unconditionally; change it to only set initialInput when the panel is not remote-backed (i.e., gate replay with the per-panel remote-backed snapshot flag used by createPanel(from:inPane:)). Concretely, compute resumeInput only if the snapshot/panel indicates a local panel (negate the per-panel remote-backed flag) and pass nil for initialInput for remote-backed panels so newTerminalSurface(inPane:focus:workingDirectory:initialInput:) never injects a local resume command into a remote startup shell.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@Sources/Workspace.swift`:
- Around line 582-599: The function resolveAndCacheResumeCommand currently gates
using the global isRemoteWorkspace and stores raw entry.resumeCommand; change it
to determine remote status per matched panel (use matchedPanelId and
panelDirectories) and skip caching if that panel is remote-backed, then build
and cache a guarded resume command using the SessionEntry helper (i.e., prepend
a cwd-guarding cd with proper shell-quoting of panelCwd so the stored command is
"cd <shell-quoted cwd> && <entry.resumeCommand>") instead of storing raw
resumeCommand; use Self.sessionAgentForKey, Self.ttyForPID, surfaceTTYNames,
SessionIndexStore.latestEntries, SessionEntry.resumeCommand and
cachedAgentResumeCommands to locate and update the code paths.
- Around line 703-708: The code currently replays
snapshot.terminal?.agentResumeCommand into newTerminalSurface via initialInput
unconditionally; change it to only set initialInput when the panel is not
remote-backed (i.e., gate replay with the per-panel remote-backed snapshot flag
used by createPanel(from:inPane:)). Concretely, compute resumeInput only if the
snapshot/panel indicates a local panel (negate the per-panel remote-backed flag)
and pass nil for initialInput for remote-backed panels so
newTerminalSurface(inPane:focus:workingDirectory:initialInput:) never injects a
local resume command into a remote startup shell.
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="Sources/Workspace.swift">
<violation number="1" location="Sources/Workspace.swift:11972">
P2: Detaching/moving a panel clears `cachedAgentResumeCommands` without transferring it, so moved live agent terminals can lose persisted auto-resume command data.</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
When cmux quits and relaunches, terminal panels that were running an agent (Claude Code, Codex, OpenCode) now automatically resume the session. At snapshot time, agent PIDs (already tracked via the socket API) are matched to terminal panels by comparing the PID's controlling TTY to each panel's surfaceTTYName. When matched, the most recent session file for that agent+cwd is looked up from disk and its resume command is stored in the session snapshot. On restore, the saved resume command is sent as initialInput to the new terminal, so the agent picks up where it left off. Supports: - Claude Code: scans ~/.claude/projects/*/ - Codex: scans ~/.codex/sessions/YYYY/MM/DD/ - OpenCode: queries ~/.local/share/opencode/opencode.db No agent CLI changes required. The agentResumeCommand field is optional in the snapshot schema, so existing sessions restore normally without it.
When cmux quits and relaunches, terminal panels that were running an agent (Claude Code, Codex, OpenCode) now automatically resume the session. Design: resume commands are cached eagerly when agent PIDs are registered via the socket API (set_agent_pid / report_status), not during the autosave snapshot. This keeps the 8s autosave hot path free of disk I/O. Flow: 1. Agent CLI reports its PID via socket -> TerminalController 2. Workspace.resolveAndCacheResumeCommand matches PID to panel via sysctl TTY, then looks up the latest session via SessionIndexStore.latestEntries 3. The resume command (including per-session flags like model/permission mode) is cached in cachedAgentResumeCommands[panelId] 4. On autosave, sessionPanelSnapshot reads the cached command (zero I/O) 5. On restore, the command is sent as initialInput to the new terminal No agent CLI changes required. No new socket API. Reuses existing SessionIndexStore loaders for correct Codex SQL, OpenCode schema, and Claude project directory resolution.
9e0a4be to
78fea9d
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
Sources/SessionIndexStore.swift (1)
522-534: Silently discarded errors from Codex/OpenCode loaders.
bagis populated insideloadCodexEntries/loadOpenCodeEntrieson schema/open failures but is then dropped when this method returns. For the resume-caching call site that's probably fine (best-effort), but it means a broken Codex DB schema will silently yield[]and the agent will never auto-resume, with no diagnostic. Consider at least adlogofbag.snapshot()under#if DEBUGso this failure mode is diagnosable.🔎 Suggested diff
nonisolated static func latestEntries( agent: SessionAgent, cwd: String, limit: Int = 1 ) async -> [SessionEntry] { let bag = ErrorBag() + let results: [SessionEntry] switch agent { case .claude: - return await loadClaudeEntries(needle: "", cwdFilter: cwd, offset: 0, limit: limit) + results = await loadClaudeEntries(needle: "", cwdFilter: cwd, offset: 0, limit: limit) case .codex: - return await loadCodexEntries(needle: "", cwdFilter: cwd, offset: 0, limit: limit, errorBag: bag) + results = await loadCodexEntries(needle: "", cwdFilter: cwd, offset: 0, limit: limit, errorBag: bag) case .opencode: - return loadOpenCodeEntries(needle: "", cwdFilter: cwd, offset: 0, limit: limit, errorBag: bag) + results = loadOpenCodeEntries(needle: "", cwdFilter: cwd, offset: 0, limit: limit, errorBag: bag) } + `#if` DEBUG + let errs = bag.snapshot() + if !errs.isEmpty { + dlog("session.latestEntries agent=\(agent.rawValue) errors=\(errs.count) first=\(errs.first ?? "")") + } + `#endif` + return results }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@Sources/SessionIndexStore.swift` around lines 522 - 534, latestEntries currently creates an ErrorBag (bag) and passes it to loadCodexEntries/loadOpenCodeEntries but then discards any errors, causing schema/open failures to be silent; update latestEntries to, under `#if` DEBUG, log bag.snapshot() (or otherwise dlog the snapshot) after calling loadCodexEntries and loadOpenCodeEntries so schema/open errors are visible during debugging while preserving current return behavior; refer to the symbols latestEntries, ErrorBag, bag.snapshot(), loadCodexEntries, loadOpenCodeEntries, and dlog/#if DEBUG when making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@Sources/SessionIndexStore.swift`:
- Around line 522-534: latestEntries currently creates an ErrorBag (bag) and
passes it to loadCodexEntries/loadOpenCodeEntries but then discards any errors,
causing schema/open failures to be silent; update latestEntries to, under `#if`
DEBUG, log bag.snapshot() (or otherwise dlog the snapshot) after calling
loadCodexEntries and loadOpenCodeEntries so schema/open errors are visible
during debugging while preserving current return behavior; refer to the symbols
latestEntries, ErrorBag, bag.snapshot(), loadCodexEntries, loadOpenCodeEntries,
and dlog/#if DEBUG when making the change.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 8f4b723d-34fd-46c2-b648-0bf40465ddcb
📒 Files selected for processing (4)
Sources/SessionIndexStore.swiftSources/SessionPersistence.swiftSources/TerminalController.swiftSources/Workspace.swift
✅ Files skipped from review due to trivial changes (1)
- Sources/TerminalController.swift
🚧 Files skipped from review as they are similar to previous changes (2)
- Sources/SessionPersistence.swift
- Sources/Workspace.swift
|
Closing - duplicates #2978 which covers the same agent session restore feature with a more comprehensive approach (menu item, shortcut, CLI). Happy to see it land from the maintainer side. |
Persists resume commands for Claude Code, Codex, and OpenCode in the session snapshot. On restore, agent sessions automatically resume in their original panes via
initialInput.Resume commands are cached when agent PIDs are registered (socket API), not at snapshot time, so the autosave hot path stays zero-cost. Remote-backed panels are excluded. Cache entries are pruned when panels close.
Test plan
Summary by CodeRabbit